Skip to content

Conversation

chanderlud
Copy link

I worked with @JosephPotapenko on this assignment.

For extra credit, we implemented an AI trivia question generator that uses the OpenAI API to generate more questions once the preset ones run out. In order to use this feature, the OPENAI_API_KEY environmental variable must be set to a valid API key.

@Copilot Copilot AI review requested due to automatic review settings September 30, 2025 04:04
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds AI-generated trivia questions to the Princess Bride trivia game using OpenAI's API. After completing the preset questions, the game continues with dynamically generated questions until the user types 'exit'.

  • Integrated OpenAI API to generate additional Princess Bride trivia questions
  • Added continuous gameplay loop with exit condition
  • Fixed integer division bug in percentage calculation

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
Program.cs Added OpenAI integration, continuous gameplay loop, and fixed percentage calculation
PrincessBrideTrivia.csproj Added OpenAI package dependency
ProgramTests.cs Added basic tests for AI question generation functionality

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

{
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey);
Assert.HasCount(4, q.Answers);
Copy link
Preview

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert.HasCount is not a valid MSTest assertion method. Use Assert.AreEqual(4, q.Answers.Length) instead.

Suggested change
Assert.HasCount(4, q.Answers);
Assert.AreEqual(4, q.Answers.Length);

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may have been the standard previously, but with the MSTests that were provided which are more current than the old standards, this is no longer the case, and .HasCount is actually correct.

Copy link

@PigPig-SDK PigPig-SDK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super cool extra credit. Though I cannot say how well the AI semantics play (I cannot be bothered getting an API key). I'm confident the try,catch,async and awaits will be functional.

}

public static bool AskQuestion(Question question)
public static (bool, bool) AskQuestion(Question question)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could replace the tuple with [flag] enum for better code clarity.
EX:
[Flags] public enum SorryImBadAtNames: byte { None = 0, AnswerdCorrectly = 1, QuitProgram = 2, };
Then you can use the objectEnum.HasFlag(FLAG); to figure out the response. More info at link:
https://learn.microsoft.com/en-us/dotnet/api/system.enum.hasflag?view=net-9.0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementing enum would be a lot messier and a less concise. However, dealing with both the correct answers and quitting the program in one function is a good suggestion and will be implemented.

Copy link

@BillMillerCoding BillMillerCoding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assignment Details
Pull Request title includes "Assignment 1" in the title ✔
Partner has also made a commit ✔
Pull Request targets Assignment 1 branch ✔
Issue 1: Application no longer crashes ✔
Issue 2: Unit test properly passes ✔
Extra Credit
Come up with a new feature for the application

Issue 3: Add a new feature request implemented ✔
Issue 3: Feature request unit tested ✔
Very impressive in my opinion! The AI feature is super cool and you guys definitely have some know how! great work

string answer2 = lines[lineIndex + 2];
string answer3 = lines[lineIndex + 3];
Question question = new() { Text = lines[lineIndex], Answers = new string[3], CorrectAnswerIndex = lines[lineIndex + 4] };
question.Answers[0] = lines[lineIndex + 1];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how in this section you condensed the code and made it more streamlined. However, I personally prefer the readability of the previous version. Since I don't see this contradicting any guidelines this is purely a stylistic opinion.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd personally chose to stick with the streamlined code because it’s cleaner and easier to maintain. It does the same thing as before but with less repetition, which reduces the chance of errors if future maintenance was to become a concern.

if (string.IsNullOrWhiteSpace(apiKey))
throw new ArgumentException("API key is required.", nameof(apiKey));

if (choices is < 3 or > 6)
Copy link

@BillMillerCoding BillMillerCoding Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very much grasping for straws here cause your code is so spot on! perhaps you should avoid making the bounds magic numbers and instead having the answer bounds be variables or consts. This would add versatility if the program ever was to be updated with more options

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! In this case, I chose to leave the bounds as-is since the program is intentionally simple and unlikely to change. Introducing constants or variables here wouldn’t add much value for this particular use case, but I appreciate the suggestion.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good discussion you two :) This is definitely what we're looking for here. Respectful discussion and explanation of your reasoning.

Side note: I would tend to agree with William here, though. I think it'd be best practice to set them in local or global fields, for future extensibility, like William said. Always sticking to best practice builds up good habits.

Copy link

@steeley21 steeley21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assignment Details

Pull Request title includes "Assignment 1" in the title ✔
Partner has also made a commit ✔
Pull Request targets Assignment 1 branch ✔
Issue 1: Application no longer crashes ✔
Issue 2: Unit test properly passes ✔

Extra Credit

Come up with a new feature for the application
    Issue 3: Add a new feature request implemented ✔
    Issue 3: Feature request unit tested ✔

Cool feature update!

JosephPotapenko and others added 6 commits October 1, 2025 21:34
…e Assert.HasCount(...) instead of Assert.AreEqual(..., collection.Length).
…n the same method in order to have more readable code.
… of the program. I changed it to the very beginning so as to be more user friendly. I also fixed the grades to display properly- whether you quit early, or if you finish everything correctly.
Copy link

github-actions bot commented Oct 2, 2025

Summary

Summary
Generated on: 10/02/2025 - 20:58:43
Coverage date: 10/02/2025 - 20:58:40
Parser: MultiReport (2x Cobertura)
Assemblies: 1
Classes: 3
Files: 3
Line coverage: 22.1% (37 of 167)
Covered lines: 37
Uncovered lines: 130
Coverable lines: 167
Total lines: 249
Branch coverage: 6% (4 of 66)
Covered branches: 4
Total branches: 66
Method coverage: Feature is only available for sponsors
Tag: 42_18205469505

Coverage

PrincessBrideTrivia - 22.1%
Name Line Branch
PrincessBrideTrivia 22.1% 6%
PrincessBrideTrivia.Program 33.6% 13.3%
PrincessBrideTrivia.Question 100%
PrincessBrideTrivia.TriviaGenerator 0% 0%

Copy link

@Joshua-Lester3 Joshua-Lester3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assignment Details

  • Pull Request title includes "Assignment 1" in the title ✔
  • Partner has also made a commit ✔
  • Pull Request targets Assignment 1 branch ✔
  • Issue 1: Application no longer crashes ✔
  • Issue 2: Unit test properly passes ✔

Extra Credit

  • Come up with a new feature for the application
    • Issue 3: Add a new feature request implemented ✔
    • Issue 3: Feature request unit tested ✔-ish

Honestly, phenomenal extra credit. That's some cool stuff. I tested it out with an OpenAI key and it worked great. The only note I'd make is change how it's tested - but we're not at a point in class that we've gone over that yet. See my comment for some information on how to go about that.

Edit: though I will say, make sure to get all the additional functionality tested and implemented well. I assume a coding assistant was used, solely because of the extra comments with check marks. Definitely encourage that if you're understanding what it's doing and can do it yourself/already did first. But quality testing and implementation comes before extra functionality with lesser quality.

Comment on lines +71 to +83
[TestMethod]
public async Task GenerateQuestions_Success()
{
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");

if (apiKey == null)
{
return; // test cannot run without the API key
}

Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey);
Assert.IsNotNull(q);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably haven't gotten to the point in class that we could test this easier, but relying on an external API for testing and CI/CD will make the test flaky (sometimes pass, sometimes fail, sometimes won't even test functionality - as it does here, and ultimately passes because no exceptions are thrown).

If you want to look ahead, Moq is a great way to mock implementations to insert "mocked" implementation for testing purposes. It's used mostly on interfaces and abstract classes, so you'd have to rearrange your architecture.

catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
continue;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling if an exception was thrown here, it wouldn't be good to continue on. That'd signal there's something wrong with the OpenAI API or your TriviaGenerator code, and it probably wouldn't fix itself between iterations. Another dangerous thought is, if it was left to run by itself and kept running into this exception, it could run down your OpenAI tokens and increase your budget, since the exception can occur before ever asking for user input.

}
Console.WriteLine("You got " + GetPercentCorrect(numberCorrect, questions.Length) + " correct");

// ✅ If user answered all file questions and we are NOT running AI loop, print score now

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments (especially, I assume, LLM comments 😉) that don't add clarity to the reader is against guidelines.

}

public static bool AskQuestion(Question question)
public static (bool answeredCorrectly, bool quitProgram) AskQuestion(Question question)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend against returning Tuples, as I think Mark said on Thursday. Could use an out parameter or maybe restructure logic around determining quitting.

if (string.IsNullOrWhiteSpace(apiKey))
throw new ArgumentException("API key is required.", nameof(apiKey));

if (choices is < 3 or > 6)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good discussion you two :) This is definitely what we're looking for here. Respectful discussion and explanation of your reasoning.

Side note: I would tend to agree with William here, though. I think it'd be best practice to set them in local or global fields, for future extensibility, like William said. Always sticking to best practice builds up good habits.

Difficulty: hard.
""";

var completion = await client.CompleteChatAsync(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd lean away from using 'var' here. Mark's guidelines advise against it here: AVOID using implicitly typed local variables ( var ) unless the data type of the assigned value is obvious.

throw new InvalidOperationException("Model returned empty content.");

// Strict JSON parse
var question = JsonSerializer.Deserialize<Question>(json, new JsonSerializerOptions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, although if the reader has experience with JsonSerializer, they could determine the type. It's not immediately obvious though.

Comment on lines +79 to +91
if (q is null) throw new InvalidOperationException("Failed to parse the model's JSON.");
if (string.IsNullOrWhiteSpace(q.Text)) throw new InvalidOperationException("Question text missing.");
if (q.Answers is null || q.Answers.Length != expectedChoices)
throw new InvalidOperationException($"Expected {expectedChoices} answers, got {q?.Answers?.Length ?? 0}.");

if (q.Answers.Any(string.IsNullOrWhiteSpace))
throw new InvalidOperationException("One or more answers are empty.");

if (!int.TryParse(q.CorrectAnswerIndex, out var idx))
throw new InvalidOperationException("CorrectAnswerIndex must be a numeric string.");

if (idx < 0 || idx >= q.Answers.Length)
throw new InvalidOperationException("CorrectAnswerIndex out of range.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (q is null) throw new InvalidOperationException("Failed to parse the model's JSON.");
if (string.IsNullOrWhiteSpace(q.Text)) throw new InvalidOperationException("Question text missing.");
if (q.Answers is null || q.Answers.Length != expectedChoices)
throw new InvalidOperationException($"Expected {expectedChoices} answers, got {q?.Answers?.Length ?? 0}.");
if (q.Answers.Any(string.IsNullOrWhiteSpace))
throw new InvalidOperationException("One or more answers are empty.");
if (!int.TryParse(q.CorrectAnswerIndex, out var idx))
throw new InvalidOperationException("CorrectAnswerIndex must be a numeric string.");
if (idx < 0 || idx >= q.Answers.Length)
throw new InvalidOperationException("CorrectAnswerIndex out of range.");
if (q is null)
{
throw new InvalidOperationException("Failed to parse the model's JSON.");
}
if (string.IsNullOrWhiteSpace(q.Text))
{
throw new InvalidOperationException("Question text missing.");
}
if (q.Answers is null || q.Answers.Length != expectedChoices)
{
throw new InvalidOperationException($"Expected {expectedChoices} answers, got {q?.Answers?.Length ?? 0}.");
}
if (q.Answers.Any(string.IsNullOrWhiteSpace))
{
throw new InvalidOperationException("One or more answers are empty.");
}
if (!int.TryParse(q.CorrectAnswerIndex, out var idx))
{
throw new InvalidOperationException("CorrectAnswerIndex must be a numeric string.");
}
if (idx < 0 || idx >= q.Answers.Length)
{
throw new InvalidOperationException("CorrectAnswerIndex out of range.");
}

Might just be a personal thing, but it's not immediately easy to read through this block. Even though certain statements and blocks can be abbreviated, I'd stick towards making them clearer. Although it's a little more typing, I'd do the suggestion above. Or even having the first two if statements not be one line total.

Here's what Mark's guidelines say about it: AVOID omitting braces, except for the simplest of single-line if statements.

# Visual Studio Version 17
VisualStudioVersion = 16.0.29215.179
VisualStudioVersion = 17.14.36518.9 d17.14
MinimumVisualStudioVersion = 10.0.40219.1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be here. Be sure to review all changes before staging/committing.

@Joshua-Lester3
Copy link

@chanderlud @JosephPotapenko make sure to respond to and resolve all comments (even Copilot's review).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants